﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.OleDb;
using System.Reflection;
using System.ComponentModel;
using System.Data.Common;

namespace Sinacor.Tools.Dado
{
    /// <summary>
    /// esta estrutura deve ser passada para a classe de infra-estrutura.
    /// </summary>
    /// <typeparam name="?"></typeparam>

    internal sealed class ResourceAccess<Entidade> : ResourceAccesInfra<OleDbConnection>
        where Entidade : class, new()
    {
        public IDbCommand getCommand(string comando, CommandType tipo)
        {
            return this.getCommand(comando, tipo, true);
        }

        private IDbCommand getCommand(string comando, CommandType tipo, bool openConnection)
        {
            IDbCommand cmd = this.Conexao.CreateCommand();
            cmd.CommandText = comando;
            cmd.CommandType = tipo;
            cmd.Connection.ConnectionString = this.getConnectionString();
            if (openConnection) cmd.Connection.Open();

            return cmd;
        }


        public IDbCommand getCommand(string comando, CommandType tipo, string conectionKey)
        {
            IDbCommand cmd = this.getCommand(comando, tipo, false);
            cmd.Connection.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings[conectionKey].ConnectionString;
            cmd.Connection.Open();
            return cmd;
        }

        public void addParameter(IDbCommand cmd, string nomeParametro, DbType tipoParametro, ParameterDirection direcao, int tamanhoParametro, object valorParametro)
        {
            if (cmd.Parameters.Contains(nomeParametro)) throw new Exception("O comando ja possui um parâmetro com nome " + nomeParametro + " associado.");

            IDbDataParameter parametro = cmd.CreateParameter();
            parametro.ParameterName = nomeParametro;
            parametro.Size = tamanhoParametro;
            parametro.DbType = tipoParametro;
            parametro.Direction = direcao;
            parametro.Value = valorParametro;
            cmd.Parameters.Add(parametro);
        }

        protected override string getConnectionString()
        {
            return System.Configuration.ConfigurationManager.ConnectionStrings["ORACLETFWD"].ConnectionString;
        }

        public List<Entidade> getListResult(IDbCommand cmd)
        {
            List<Entidade> entidades = new List<Entidade>();
            Entidade entidade = null;

            using (IDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
            {
                while (reader.Read())
                {
                    entidade = new Entidade();
                    this.PopulateClass(reader, entidade);
                    entidades.Add(entidade);
                }
                reader.Close();
            }
            return entidades;
        }

        /// <summary>
        /// este método usa apenas a primeira linha do reader e despreza o restante.
        /// </summary>
        /// <param name="cmd">interface do command .net (pode ser passado qualquer tipo de command).</param>
        /// <returns></returns>
        public Entidade getResult(IDbCommand cmd)
        {
            Entidade entidade = new Entidade();
            using (IDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
            {
                if (reader.Read())
                {
                    this.PopulateClass(reader, entidade);
                }
                reader.Close();
            }
            return entidade;
        }



        /// <summary>
        /// Popula membros da classe com o mesmo nome de coluna do DataReader 
        /// </summary>
        /// <param name="preader"></param>
        /// <param name="pInstance"></param>
        private void PopulateClass(IDataReader reader, object instance)
        {
            try
            {
                Type oObjectType;
                PropertyInfo oPropertyInfo;
                if (instance == null)
                    return;
                oObjectType = instance.GetType();
                int i = 0;
                for (i = 0; i <= (reader.FieldCount - 1); i++)
                {
                    oPropertyInfo = oObjectType.GetProperty(reader.GetName(i));
                    this.SetPropertyValue(instance, reader.GetName(i), reader[i]);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Erro ao popular a Classe (PopulateClass):" + ex.Message);
            }
        }

        /// <summary>
        /// Popula membros da classe com o mesmo nome de coluna do DataTable
        /// </summary>
        /// <param name="pDataRow">DataRow</param>
        /// <param name="pInstance">Objeto a ser populado</param>
        private void PopulateClass(DataRow dataRow, object instance)
        {
            try
            {
                Type oObjectType;
                PropertyInfo oPropertyInfo;

                if (instance == null)
                    return;

                oObjectType = instance.GetType();

                int i = 0;
                for (i = 0; i < dataRow.Table.Columns.Count; i++)
                {
                    oPropertyInfo = oObjectType.GetProperty(dataRow.Table.Columns[i].ColumnName);
                    this.SetPropertyValue(instance, dataRow.Table.Columns[i].ColumnName, dataRow[dataRow.Table.Columns[i].ColumnName]);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Erro ao popular a Classe (PopulateClass):" + ex.Message);
            }
        }

        /// <summary>
        /// Atrimui o valor a popriedade
        /// </summary>
        /// <param name="pInstance"></param>
        /// <param name="pPropertyName"></param>
        /// <param name="oValue"></param>
        /// <returns></returns>
        private void SetPropertyValue(object pInstance, string pPropertyName, object pPropertyValue)
        {
            string[] props = pPropertyName.Split(".".ToCharArray());
            for (int i = 0; i < props.Length; i++)
            {
                if (pInstance == null)
                    throw new Exception(" Erro em SetPropertyValue: Propriedade sem instância!");
                PropertyInfo prop = pInstance.GetType().GetProperty(props[i]);
                if (prop == null)
                    break;

                if (i == (props.Length - 1)) //última propriedade
                {
                    pPropertyValue = this.ChangeValueToPropertyType(pPropertyValue, prop);
                    prop.SetValue(pInstance, pPropertyValue, null);
                }
                else //passa para a próxima propriedade
                    pInstance = prop.GetValue(pInstance, null);
            }
        }

        /// Muda o tipo do valor de acordo com a propriedade
        /// </summary>
        /// <param name="property">Propriedade do model</param>
        /// <param name="objValue">Valor da propriedade</param>
        /// <returns>Object tipado para a propriedade</returns>
        private object ChangeValueToPropertyType(object objValue, PropertyInfo property)
        {

            TypeConverter converter = TypeDescriptor.GetConverter(property.PropertyType);
            objValue = objValue == DBNull.Value ? null : objValue;

            if ((this.IsNullAbleType(property.PropertyType) && objValue == null)
                || (objValue != null && converter.CanConvertFrom(objValue.GetType())))
            {
                return converter.ConvertFrom(objValue);
            }
            else if (converter.CanConvertFrom(typeof(string)))
            {
                return converter.ConvertFrom(Convert.ToString(objValue));
            }

            throw new InvalidCastException("The value " + objValue != null ? Convert.ToString(objValue) : "null" + " can not be cast to " + property.PropertyType.Name + " type.");
        }

        private bool IsNullAbleType(Type tipo)
        {
            return tipo.IsGenericType && tipo.GetGenericTypeDefinition() == typeof(Nullable<>);
        }

    }


}